!pr2
!lm12
!rm75
Implementing New Opcodes Using 'BRK'.......Bob Sander-Cederlof

If you have the Autostart ROM, you can control what happens when a BRK instruction is executed.  If you do nothing, a BRK will cause entry into the Apple Monitor, and the register contents will be displayed.  But (if you have the Autostart Monitor) by a small amount of programming you can make the BRK do marvelous things.

Like simulate neat instructions from the 6809, which are not in the 6502, for example.  I am thinking particularly of the LEAX instruction, which loads the effective address into a 16-bit register; of BSR, which enters a subroutine like JSR, but with a relative address; and of BRA, which is a relatively addressed JMP.  With these three instructions you can write position-independent programs (programs that execute properly without any modification regardless of where they are loaded in memory).

I am thinking of these because of an article by A. Sato in "Lab Letters" (a publication of ESD Laboratories in Tokyo, JAPAN) Volume 6 No. 1, pages 91-93.  It is all written in Japanese (see example below), but I think I deciphered what he is saying.

When a BRK instruction is executed, the program is interrupted as though a Non-Maskable Interrupt (NMI) occurred.  The B bit in the status register is set, so the Apple can tell that the interrupt was caused by BRK rather than some external event.  After making this determination, the Autostart Monitor performs a "JMP ($3F0)" instruction.  This means that you can get control by placing the address of your own program into $3F0 and $3F1.  The monitor initialization process puts the address $FA59 there.

By the time the monitor branches to the BRK processor (its own or yours) all the registers have been saved.  The address of the BRK instruction plus 2 (PC) has been saved at $3A and $3B; the registers A, X, Y, P (status), and S (stack pointer) have been saved in $45 through $49, respectively.


BRK Interceptor/Interpreter

In the program below, lines 1180-1230 will set up the BRK-vector at $3F0 and $3F1 to point to your own BRK processor.  Lines 1250-1320 back up the PC value by one, to point at the byte immediately following the BRK instruction.  At this point I can decide what to do about the BRK.

Since I want to simulate the operation of LEAX, BSR, and BRA, I will use the BRK instruction to introduce a pseudo instruction of three bytes.  I decided to copy A. Sato on this.  LEAX is a BRK instruction followed by LDX from an absolute address.  This is $AE in hexadecimal, followed by a 16-bit value representing a relative address.  BSR is BRK followed by a JSR instruction ($20) and a relative address; BRA is BRK followed by a JMP instruction ($4C) and a relative address.

Looking back at the program, lines 1310 and 1320 store the address of the secondary opcode byte into PNTR and PNTR+1.  These two bytes are inside an instruction at line 1760.  I didn't want to use any page-zero space, so I had to resort to this kind of self-modifying code.  While we are here, lines 1750-1780 pick up the byte whose address is in PNTR.  Lines 1710-1740 increment PNTR.  If we call GET.THIS.BYTE, it just picks up the byte currently pointed at.  If we call GET.NEXT.BYTE, it increments the pointer and gets the next byte.

Lines 1330-1370 pick up the three bytes which follow the BRK.  The opcode byte is saved in the Y-register.  Lines 1380-1450 compute the effective address, by adding the actual address of the instruction to the relative address inside the instruction.

Lines 1470-1540 classify the opcode; if it is one of the three we have implemented, it branches to the appropriate code.  If not, it jumps back into the monitor and processes the BRK in the normal monitor way.


Opcode Implementation

Lines 1560-1780 implement the three opcodes BSR, BRA, and LEAX.  BRA (Branch Always) is the easiest one.  We have already computed the effective address and stored it in the address field of the JMP instruction at line 1620.  All BRA does is restore the registers (line 1610), and JMP to the effective address.

BSR (Branch to Subroutine) is only slightly harder.  We first have to push the return address on the stack, and then do a BRA.  Lines 1560-1590 do the pushing.

LEA (Load Effective Address) is the hardest.  Lines 1650-1690 do the work.  First GET.NEXT.BYTE moves the address in PNTR,PNTR+1 to point at the first byte of the next instruction.  That is so we can continue exectution.  Then MON.RESTORE gets back the original contents of all the registers.  THEN LDY and LDX pick up the effective address in the Y- and X-registers.  The high byte of the effective address is in the X-register, and the Z- and N-bits in the status register reflect the value of this byte.  If you wish, you could modify this to not change the status by inserting a PHP opcode after line 1660, and PLP after line 1680; then the status register would remain unchanged by the entire LEA process.  Or you could reverse lines 1670 and 1680, so that the status reflected the low-order byte of the effective address.


Demonstration Using the New Opcodes

Lines 1800 and beyond are a demonstration of the use of the new opcodes.  First I defined some macros for the new opcodes.  I didn't have to do this, but it is convenient if you have a macro assembler.  If you don't, you can use the BRK instruction on one line, followed by a LDX, JSR, or JMP instruction with a relative address on the next line.

My macros are defined in a nested fashion.  The BRK macro generates two lines:  BRK on the first line, and a second line consisting of the specified opcode and operand.  The LEA, BSR, and BRA macros call BRK to generate LDX, JSR, and JMP instructions after the BRK.  The operand field is a relative address, computed within the BRK macro.

The demonstration program will run anywhere in memory, as long as the BRK interpreter has been loaded and initialized.  You can test this by moving $871-89F to other places and running it.  What it does is print out the message in line 2090.
